package com.zuipin.util;

import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.io.UnsupportedEncodingException;
import java.net.HttpURLConnection;
import java.net.URL;
import java.security.KeyFactory;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

public class AliPayUtil {
	private Log					log						= LogFactory.getLog(AliPayUtil.class);
	public static final String	ALIPAY_GATEWAY_NEW		= "https://mapi.alipay.com/gateway.do?";
	//支付宝消息验证地址
	private static final String	HTTPS_VERIFY_URL		= "https://mapi.alipay.com/gateway.do?service=notify_verify&";
	
	/** 更新订单状态锁 */
	private static byte[]		UP_ORDER_STATUS_LOCK	= new byte[0];
	public static final String	HEX						= "0123456789abcdef";
	
	/**
	 * 
	* @Description:
	* @Title: 
	* @author: xujinding
	 * @throws NoSuchAlgorithmException 
	* @date: 2017年4月5日下午6:01:16
	 */
	public static String bulidAliForm(String body, String price, String subject,
			String out_trade_no, Map<String, String> toolMap)
			throws UnsupportedEncodingException, NoSuchAlgorithmException {
		//官方文档--手机网站支付接口
		//https://doc.open.alipay.com/docs/doc.htm?spm=a219a.7629140.0.0.Qa9HGx&treeId=60&articleId=104790&docType=1		
		// 把请求参数打包成数组
		Map<String, String> sParaTemp = new HashMap<String, String>();
		sParaTemp.put("service", "create_direct_pay_by_user");
		sParaTemp.put("partner", toolMap.get("partner"));
		sParaTemp.put("_input_charset", AlipayConfig.input_charset);
		//sign_type和sign在buildRequestPara()拼上
		sParaTemp.put("notify_url", toolMap.get("notifyUrl"));
		sParaTemp.put("return_url", toolMap.get("returnUrl"));
		sParaTemp.put("out_trade_no", out_trade_no);
		sParaTemp.put("subject", subject);
		sParaTemp.put("total_fee", price);
		sParaTemp.put("seller_id", toolMap.get("sellerId"));
		sParaTemp.put("payment_type", AlipayConfig.payment_type);
		//sParaTemp.put("show_url", toolMap.get("showUrl"));
		sParaTemp.put("body", body);
		// 建立请求
		String sHtmlText = buildRequest(sParaTemp, "post", "确认", toolMap.get("privateKey"),
				toolMap.get("keyValue"));
		System.out.println(sHtmlText);
		
		return sHtmlText;
	}
	
	/**
	 * 建立请求，以表单HTML形式构造（默认）
	 * 
	 * @param sParaTemp 请求参数数组
	 * @param strMethod 提交方式。两个值可选：post、get
	 * @param strButtonName 确认按钮显示文字
	 * @return 提交表单HTML文本
	 * @throws UnsupportedEncodingException 
	 * @throws NoSuchAlgorithmException 
	 */
	public static String buildRequest(Map<String, String> sParaTemp, String strMethod,
			String strButtonName, String privateKey, String key)
			throws NoSuchAlgorithmException, UnsupportedEncodingException {
		// 待请求参数数组
		Map<String, String> sPara = buildRequestPara(sParaTemp, privateKey, key);
		List<String> keys = new ArrayList<String>(sPara.keySet());
		
		StringBuffer sbHtml = new StringBuffer();
		
		sbHtml.append("<form id=\"alipaysubmit\" name=\"alipaysubmit\" action=\""
				+ ALIPAY_GATEWAY_NEW + "_input_charset=" + AlipayConfig.input_charset
				+ "\" method=\"" + strMethod + "\">");
		
		for (int i = 0; i < keys.size(); i++) {
			String name = (String) keys.get(i);
			String value = (String) sPara.get(name);
			
			sbHtml.append("<input type=\"hidden\" name=\"" + name + "\" value=\"" + value + "\"/>");
		}
		
		// submit按钮控件请不要含有name属性
		sbHtml.append("<input type=\"submit\" value=\"" + strButtonName
				+ "\" style=\"display:none;\"></form>");
		sbHtml.append("<script>document.forms['alipaysubmit'].submit();</script>");
		
		return sbHtml.toString();
	}
	
	/**
	 * 生成要请求给支付宝的参数数组
	 * 
	 * @param sParaTemp 请求前的参数数组
	 * @return 要请求的参数数组
	 * @throws UnsupportedEncodingException 
	 * @throws NoSuchAlgorithmException 
	 */
	private static Map<String, String> buildRequestPara(Map<String, String> sParaTemp,
			String privateKey, String key)
			throws NoSuchAlgorithmException, UnsupportedEncodingException {
		// 除去数组中的空值和签名参数
		Map<String, String> sPara = AlipayCore.paraFilter(sParaTemp);
		// 生成签名结果
		String mysign = buildRequestMysign(sPara, privateKey, key);
		
		// 签名结果与签名方式加入请求提交参数组中
		sPara.put("sign", mysign);
		sPara.put("sign_type", AlipayConfig.sign_type);
		
		return sPara;
	}
	
	/**
	 * 生成签名结果
	 * 
	 * @param sPara 要签名的数组
	 * @return 签名结果字符串
	 * @throws NoSuchAlgorithmException 
	 * @throws UnsupportedEncodingException 
	 */
	public static String buildRequestMysign(Map<String, String> sPara, String privateKey,
			String key) throws NoSuchAlgorithmException, UnsupportedEncodingException {
		// 把数组所有元素，排序并按照“参数=参数值”的模式用“&”字符拼接成字符串
		String prestr = AlipayCore.createLinkString(sPara);
		String mysign = "";
		if (AlipayConfig.sign_type.equals("MD5")) {
			String text = prestr + key;
			MessageDigest md5 = MessageDigest.getInstance("MD5");
			byte[] signByte = md5.digest(text.getBytes("utf-8"));
			//将字节转成16进制的字符串;
			mysign = bytes2hex(signByte);
			return mysign;
		}
		if (AlipayConfig.sign_type.equals("RSA")) {
			mysign = signRSA(prestr, privateKey, AlipayConfig.input_charset);
		}
		return mysign;
	}
	
	/**
	 * 
	* @Description:
	* @Title: 
	* @author: xujinding
	* @date: 2017年4月6日上午11:50:00
	 */
	public static String bytes2hex(byte[] bytes) {
		StringBuilder sb = new StringBuilder(bytes.length * 2);
		for (byte b : bytes) {
			// 取出这个字节的高4位，然后与0x0f与运算，得到一个0-15之间的数据，通过HEX.charAt(0-15)即为16进制数    
			sb.append(HEX.charAt((b >> 4) & 0x0f));
			// 取出这个字节的低位，与0x0f与运算，得到一个0-15之间的数据，通过HEX.charAt(0-15)即为16进制数    
			sb.append(HEX.charAt(b & 0x0f));
		}
		return sb.toString();
	}
	
	/////////////////////////////////支付宝RSA相关-----start////////////////////////////////
	/**
	* RSA签名
	* @param content 待签名数据
	* @param privateKey 商户私钥
	* @param input_charset 编码格式
	* @return 签名值
	*/
	public static String signRSA(String content, String privateKey, String input_charset) {
		try {
			PKCS8EncodedKeySpec priPKCS8 = new PKCS8EncodedKeySpec(Base64.decode(privateKey));
			KeyFactory keyf = KeyFactory.getInstance("RSA");
			PrivateKey priKey = keyf.generatePrivate(priPKCS8);
			
			java.security.Signature signature = java.security.Signature.getInstance("SHA1WithRSA");
			
			signature.initSign(priKey);
			signature.update(content.getBytes(input_charset));
			
			byte[] signed = signature.sign();
			
			return Base64.encode(signed);
		} catch (Exception e) {
			e.printStackTrace();
		}
		
		return null;
	}
	
	/**
	* RSA验签名检查
	* @param content 待签名数据
	* @param sign 签名值
	* @param ali_public_key 支付宝公钥
	* @param input_charset 编码格式
	* @return 布尔值
	*/
	public static boolean verifyRSA(String content, String sign, String ali_public_key,
			String input_charset) {
		try {
			KeyFactory keyFactory = KeyFactory.getInstance("RSA");
			byte[] encodedKey = Base64.decode(ali_public_key);
			PublicKey pubKey = keyFactory.generatePublic(new X509EncodedKeySpec(encodedKey));
			
			java.security.Signature signature = java.security.Signature.getInstance("SHA1WithRSA");
			
			signature.initVerify(pubKey);
			signature.update(content.getBytes(input_charset));
			
			boolean bverify = signature.verify(Base64.decode(sign));
			return bverify;
			
		} catch (Exception e) {
			e.printStackTrace();
		}
		
		return false;
	}
	
	/////////////////////////////////支付宝RSA相关-----end////////////////////////////////
	
	/**
	 * 验证消息是否是支付宝发出的合法消息
	 * @param params 通知返回来的参数数组
	 * @return 验证结果
	 * @throws UnsupportedEncodingException 
	 * @throws NoSuchAlgorithmException 
	 */
	public static boolean verify(Map<String, String> params, String publicKey, String key)
			throws NoSuchAlgorithmException, UnsupportedEncodingException {
		
		//判断responsetTxt是否为true，isSign是否为true
		//responsetTxt的结果不是true，与服务器设置问题、合作身份者ID、notify_id一分钟失效有关
		//isSign不是true，与安全校验码、请求时的参数格式（如：带自定义参数等）、编码格式有关
		String responseTxt = "true";
		if (params.get("notify_id") != null) {
			String notify_id = params.get("notify_id");
			responseTxt = verifyResponse(notify_id);
		}
		String sign = "";
		if (params.get("sign") != null) {
			sign = params.get("sign");
		}
		boolean isSign = getSignVeryfy(params, sign, publicKey, key);
		
		//写日志记录（若要调试，请取消下面两行注释）
		//String sWord = "responseTxt=" + responseTxt + "\n isSign=" + isSign + "\n 返回回来的参数：" + AlipayCore.createLinkString(params);
		//AlipayCore.logResult(sWord);
		
		if (isSign && responseTxt.equals("true")) {
			return true;
		} else {
			return false;
		}
	}
	
	/**
	 * 根据反馈回来的信息，生成签名结果
	 * @param Params 通知返回来的参数数组
	 * @param sign 比对的签名结果
	 * @return 生成的签名结果
	 * @throws NoSuchAlgorithmException 
	 * @throws UnsupportedEncodingException 
	 */
	private static boolean getSignVeryfy(Map<String, String> Params, String sign, String publicKey,
			String key) throws NoSuchAlgorithmException, UnsupportedEncodingException {
		//过滤空值、sign与sign_type参数
		Map<String, String> sParaNew = AlipayCore.paraFilter(Params);
		//获取待签名字符串
		String preSignStr = AlipayCore.createLinkString(sParaNew);
		//获得签名验证结果
		boolean isSign = false;
		if (AlipayConfig.sign_type.equals("RSA")) {
			isSign = verifyRSA(preSignStr, sign, publicKey, AlipayConfig.input_charset);
		} else if (AlipayConfig.sign_type.equals("MD5")) {
			String text = preSignStr + key;
			MessageDigest md5 = MessageDigest.getInstance("MD5");
			byte[] signByte = md5.digest(text.getBytes("utf-8"));
			//将字节转成16进制的字符串;
			String mysign = bytes2hex(signByte);
			if (mysign.equals(sign)) {
				isSign = true;
			} else {
				isSign = false;
			}
		}
		return isSign;
	}
	
	/**
	* 获取远程服务器ATN结果,验证返回URL
	* @param notify_id 通知校验ID
	* @return 服务器ATN结果
	* 验证结果集：
	* invalid命令参数不对 出现这个错误，请检测返回处理中partner和key是否为空 
	* true 返回正确信息
	* false 请检查防火墙或者是服务器阻止端口问题以及验证时间是否超过一分钟
	*/
	private static String verifyResponse(String notify_id) {
		//获取远程服务器ATN结果，验证是否是支付宝服务器发来的请求
		
		String partner = AlipayConfig.partner;
		String veryfy_url = HTTPS_VERIFY_URL + "partner=" + partner + "&notify_id=" + notify_id;
		
		return checkUrl(veryfy_url);
	}
	
	/**
	* 获取远程服务器ATN结果
	* @param urlvalue 指定URL路径地址
	* @return 服务器ATN结果
	* 验证结果集：
	* invalid命令参数不对 出现这个错误，请检测返回处理中partner和key是否为空 
	* true 返回正确信息
	* false 请检查防火墙或者是服务器阻止端口问题以及验证时间是否超过一分钟
	*/
	private static String checkUrl(String urlvalue) {
		String inputLine = "";
		
		try {
			URL url = new URL(urlvalue);
			HttpURLConnection urlConnection = (HttpURLConnection) url.openConnection();
			BufferedReader in = new BufferedReader(
					new InputStreamReader(urlConnection.getInputStream()));
			inputLine = in.readLine().toString();
		} catch (Exception e) {
			e.printStackTrace();
			inputLine = "";
		}
		
		return inputLine;
	}
	
}
